package lol.clann.bukkit.pluginboot.command;

import com.google.common.collect.ImmutableList;
import lol.clann.bukkit.pluginboot.model.rawmessage.RawMessage;
import lol.clann.bukkit.pluginboot.util.PageUtils;
import lol.clann.bukkit.pluginboot.util.PlayerUtils;
import lol.clann.bukkit.pluginboot.util.ServerUtils;
import lol.clann.bukkit.pluginboot.util.TextUtils;
import lol.clann.clannboot.annotation.inject.Inject;
import lol.clann.clannboot.annotation.load.PostLoad;
import lol.clann.clannboot.annotation.load.Unload;
import lol.clann.clannboot.context.ClassContext;
import lol.clann.clannboot.module.Module;
import lol.clann.clannboot.util.BaseUtils;
import lol.clann.clannboot.util.Joiner;
import lol.clann.clannboot.util.clazz.ClassUtils;
import org.apache.commons.lang.Validate;
import org.apache.commons.lang3.StringUtils;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandException;
import org.bukkit.command.CommandSender;
import org.slf4j.Logger;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author pyz
 * @date 2018/7/22 下午3:19
 */
public abstract class ICommand extends Command {

    @Inject
    private Logger $logger;
    @Inject
    private PlayerUtils $playerUtils;
    @Inject
    private TextUtils $textUtils;
    @Inject
    private PageUtils $pageUtils;
    @Inject
    private ServerUtils $serverUtils;

    private Map<String, SubCommand> subCommands = new LinkedHashMap<>();
    private Module module;

    protected ICommand(String cmd) {
        this(cmd, null, null, null);
    }

    /**
     * @param cmd          指令/clann -> clann
     * @param description  指令描述,可null
     * @param usageMessage 用法,可null
     * @param aliases      别名,可null
     */
    protected ICommand(String cmd, String description, String usageMessage, List<String> aliases) {
        super(cmd);
        module = ClassContext.getModule(this.getClass());
        if (StringUtils.isNotBlank(description)) {
            this.setDescription(description);
        }
        if (StringUtils.isNotBlank(usageMessage)) {
            this.setDescription(usageMessage);
        }
        if (aliases != null && !aliases.isEmpty()) {
            this.setAliases(aliases);
        }
    }

    @Override
    public List<String> tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException {
        Validate.notNull(sender, "Sender cannot be null");
        Validate.notNull(args, "Arguments cannot be null");
        Validate.notNull(alias, "Alias cannot be null");
        if (args.length == 0) {
            return ImmutableList.of();
        }
        if (args.length == 1) {
            //补全第一个
            String command = args[0];
            List<String> result = subCommands.entrySet().stream()
                    .filter(en -> hasPermission(en.getValue(), sender, false))
                    .map(en -> en.getKey())
                    .filter(key -> key.startsWith(command))
                    .collect(Collectors.toList());
            return result;
        }
        return super.tabComplete(sender, alias, args);
    }

    @Override
    public boolean execute(CommandSender sender, String s, String[] args) {
        if (!this.testPermission(sender)) {
            return false;
        }
        if (args.length == 0) {
            singleCommand(sender);
            return true;
        } else if (args[0].equalsIgnoreCase("help")) {
            help(sender);
            return true;
        } else {
            String subCommand = args[0];
            String[] params = new String[args.length - 1];
            System.arraycopy(args, 1, params, 0, params.length);
            SubCommand sub = subCommands.get(subCommand.toLowerCase());
            if (sub != null) {
                try {
                    if (!hasPermission(sub, sender, true)) {
                        return false;
                    }
                } catch (CommandException e) {
                    sender.sendMessage(e.getMessage());
                    return false;
                }
                Object[] parseResult = null;
                try {
                    for (int i = 0; i < params.length; i++) {
                        params[i] = params[i].replace("&", "§");
                    }
                    parseResult = ParamParsers.parse(sender, sub.method, params);
                } catch (ParameterParseException e) {
                    sender.sendMessage(e.getMessage());
                    help(sender, sub);
                    return false;
                }
                handle(sender, sub, args, parseResult);
                return true;
            } else {
                try {
                    otherSubCommands(sender, args);
                } catch (Throwable e) {
                    parseException(sender, args, e);
                }
            }
        }
        return true;
    }

    /**
     * 子类可覆盖此方法.来改变默认行为
     *
     * @param sender
     * @return
     */
    public boolean singleCommand(CommandSender sender) {
        help(sender);
        return true;
    }

    @PostLoad
    public final void $load() {
        ClassUtils.getAllMethods(this.getClass(), wm -> !wm.isStatic() && !wm.isAbstrct())
                .forEach(wm -> {
                            CommandAnnotation sub = wm.getAnnotation(CommandAnnotation.class);
                            if (sub == null) {
                                return;
                            }
                            SubCommand subCommand = new SubCommand(sub, wm.getHandle());
                            subCommand.setDesc(BaseUtils.resolve(module.getId(), sub.des())[0]);
                            subCommands.put(wm.getName().toLowerCase(), subCommand);
                        }
                );
        $logger.info("注册指令:" + this.getName());
        for (SubCommand subCommand : subCommands.values()) {
            help(Bukkit.getConsoleSender(), subCommand);
        }
        $serverUtils.getCommandMap().register(this.module.getId(), this);
    }

    @Unload
    public void $unload() {
        this.unregister($serverUtils.getCommandMap());
    }

    /**
     * 子命令
     *
     * @param sender
     * @param sub
     */
    public final void help(CommandSender sender, SubCommand sub) {
        getHelpMessage(sub).send(sender);
    }

    private RawMessage getHelpMessage(SubCommand sub) {
        return RawMessage.createRawMessage(("§6/" + this.getName() + " §b" + sub.method.getName() + "§3 " + sub.annotation.args()).trim())
                .runCommand("/" + this.getName() + " " + sub.method.getName())
                .then("    §6-§a " + sub.getDesc());
    }

    /**
     * 所有子命令
     *
     * @param sender
     */
    public final void help(CommandSender sender) {
        List<RawMessage> helps = subCommands.values().stream()
                .filter(sub -> hasPermission(sub, sender, false))
                .map(sub -> getHelpMessage(sub))
                .collect(Collectors.toList());
        $pageUtils.sendPage(sender, helps, "§6===============§b帮助§6===============");
    }

    /**
     * 处理其他子命令 参数含有子命令..
     *
     * @param sender
     * @param args   子命令 子命令参数...
     */
    public void otherSubCommands(CommandSender sender, String args[]) {
        help(sender);
    }

    /**
     * 权限判断
     *
     * @param sub
     * @param sender
     * @param notice
     * @return
     */
    private boolean hasPermission(SubCommand sub, CommandSender sender, boolean notice) {
        if (sub.annotation.mustPlayer() && !$playerUtils.isPlayer(sender)) {
            if (notice) {
                throw new CommandException("控制台无法执行此指令");
            }
            return false;
        }
        if (sub.annotation.needOp() && !sender.isOp()) {
            if (notice) {
                throw new CommandException("§4权限不足");
            }
            return false;
        }
        if (!sub.annotation.permission().isEmpty() && !sender.hasPermission(sub.annotation.permission())) {
            if (notice) {
                throw new CommandException("§4权限不足:§2" + sub.annotation.permission());
            }
            return false;
        }
        return true;
    }

    /**
     * 反射执行
     *
     * @param sub
     * @param sender
     * @param args
     * @throws Exception
     */
    private boolean handle(CommandSender sender, SubCommand sub, String[] origin, Object[] args) {
        try {
            Object result = sub.method.invoke(this, args);
            if (result instanceof Boolean) {
                return (Boolean) result;
            }
            if (result instanceof String) {
                sender.sendMessage((String) result);
            }
            return true;
        } catch (Throwable e) {
            parseException(sender, origin, e);
            return false;
        }
    }

    private void parseException(CommandSender sender, String[] origin, Throwable e) {
        Throwable ce;
        StringBuilder sb = new StringBuilder("指令执行过程中抛出");
        ce = e;
        while (ce != null && !(ce instanceof CommandException)) {
            ce = ce.getCause();
        }
        if (ce != null) {
            sender.sendMessage("§c" + ce.getMessage());
        } else {
            $logger.error(String.format("%s指令失败/%s %s", sender.getName(), getName(), Joiner.join(origin, " ")), e);
            // 反射会包一层InvocationTargetException异常,这里要解开
            sb.append(e.getCause().getClass().getName() + "异常:" + e.getMessage());
            sender.sendMessage("§c" + sb.toString());
        }
    }
}

